Programming languages continue to evolve to facilitate specification by programmers as well as efficient execution. In the early days of computer languages, low-level machine code was prevalent. With machine code, a computer program or instructions comprising a computer program were written with machine languages or assembly languages and executed by the hardware (e.g., microprocessor). These languages provided an efficient means to control computing hardware, but were very difficult for programmers to comprehend and develop sophisticated logic. Subsequently, languages were introduced that provided various layers of abstraction. Accordingly, programmers could write programs at a higher level with a higher-level source language, which could then be converted via a compiler or interpreter to the lower level machine language understood by the hardware. Further advances in programming have provided additional layers of abstraction to allow more advanced programming logic to be specified much quicker then ever before. However, these advances do not come without a processing cost.
Compilers and/or interpreters bear the burden of translating high-level logic into executable machine code. In general, a compilers and/or interpreters are components that receive a program specified in a source programming language (e.g., C, C#, Visual Basic, Java . . . ) and convert the logic provided thereby to machine language that is executable by a hardware device. However, the conversion need not be done verbatim. In fact, conventional compilers and/or interpreters analyze the source code and generate very efficient code. For example, programmers write code that sets forth a logical flow of operations that is intuitive and easy for humans to understand, but is often inefficient for a computer to execute. Compilers and/or interpreters can identify inefficiencies and improve program performance at the hardware level by eliminating unnecessary operations and/or rearranging the execution of instructions while still achieving the intended results. In this manner, programmers can create robust and efficient software programs.
Artificial restrictions may exist in various programming languages due to requirements of the Common Language Runtime (CLR) environments for example. Consider the following code in Visual Basic that defines the types A, B, C.
Class A . . .
Class B: Inherits A . . .
Class C: Inherits B . . .
Such can define a class hierarchy, wherein any instance of C may be legally substituted wherever an instance of B or A would be expected, and also any instance of B may be legally substituted wherever an instance of A would be expected. This substitutability defines “subtype”, wherein class B is a subtype of class A, and that class C is a subtype of both class B and class A, or, symbolically, C<: B<: A. Put differently, “every B is an A, and every C is a B and an A”—(Technically, distinctions exist between “subclass” and “subtype,” yet such differences are typically not germane for purposes of the subject innovation.)
The following substitution are considered in context of the following exemplary functions, and it is to be appreciated that other sub statements or procedures can also be implemented
Function F(X As A) As C . . . REM F has type A→C
Function G(X As C) As A . . . REM G has type C→A
Function H(X As B) As B . . . REM H has type B→B
Designating B1 and B2 as two instances of type B, and considering the following assignment statements, which have typed variables on their left-hand sides and function calls on their right-hand sides:
B2=F(B1)
Since B1 is an A and F expects an A in its input position, the input is type-safe, as it follows the substitutability criterion above. Since F(B1) is of type C, and every C is a B, the assignment of F(B1) to B2 is also type-safe. Such requires no special treatment, since the type conversions of both the argument and return value are so-called “upcasts,” in the normal direction implied by the “is-a” relationship. Such type manipulation can be summarized as follows:
F:: A → C B1 :: B B <: AREM if F has type A → C, b1 has type B, and-----------------------------------------------REM B is a subtype of A, thenF(B1):: CREM the call F(b1) has type C
The rule for assignments looks/checks that the type of the rhs is substitutable for the type of the lhs:
F(B1) :: C B2 B C <: BREM if rhs of the assignment hastype C and----------------------------------------REM C is a subtype of B, thenB2 = F(B1) is validREM it can be assigned to the lhsB2 :: B
Likewise, considering the following case of:
B2=G(B1)
Since G expects a C in its argument position, a B cannot be freely passed. While every C is a B, it is not the case that every B is a C. As such, the user or the compiler must typically insert an explicit “downcast” type conversion on the argument B1implying a run-time check of its dynamic type. Similarly, the instance of type A returned by G must be downcast to type B to complete the assignment. So, either the user or the compiler must typically rewrite the statement as follows:    B2=Ctype (G (Ctype (B1, C)), B)
Moreover, the call of H creates no difficulty, since both input and return types match exactly. It is trivially type-safe:    B2=H (B1)
In general, the current CLR and .NET programming languages only permit the H-form for delegates, in other words, the types must match exactly when calling a function through a delegate. Such can create an inconsistent and counter intuitive programming restriction that hinders flexibility of programming.
Therefore, there is a need to overcome the aforementioned exemplary deficiencies associated with conventional systems and devices.